package xml

import (
	"encoding/xml"
	"fmt"
	"io"
	. "org.xx/kris2/common"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

var (
	dbSources  []string
	dbObjects  []DbObject
	dbEntities []*DbEntity
	dbModules  []*DbModule

	currentFileTimestamp time.Time
	currentElementAttrs  map[string]string
)

func indent(n int) string {
	return strings.Repeat("  ", n)
}

// rawAttr 获取字符串类型的原始属性值，不做任何处理。
func rawAttr(key string, defaultValue string) string {
	v, ok := currentElementAttrs[key]
	if !ok || v == "auto" {
		return defaultValue
	} else {
		return v
	}
}

// stringAttr 获取字符串类型的属性值, 自动去掉首尾空格。
func stringAttr(key string, defaultValue string) string {
	v := rawAttr(key, defaultValue)
	return strings.TrimSpace(v)
}

// nameAttr 获取用于表示名字的属性值，自动去掉首尾空格，去掉特殊字符，并且转为小写。
func nameAttr(key string) string {
	v := rawAttr(key, "")
	return strings.ToLower(strings.Map(func(r_ rune) rune {
		if (r_ >= '0' && r_ <= '9') || (r_ >= 'a' && r_ <= 'z') || (r_ >= 'A' && r_ <= 'Z') || r_ == '_' || r_ == '-' {
			return r_
		} else {
			return -1
		}
	}, strings.TrimSpace(v)))
}

// classAttr 用于获取完整类型名的属性值，自动去掉首尾空格并且转为小写。
func classNameAttr(key string) string {
	v := rawAttr(key, "")
	return strings.ToLower(strings.TrimSpace(v))
}

// 获取整数类型的属性值。
func intAttr(key string, defaultValue int) int {
	v, ok := currentElementAttrs[key]
	if !ok || v == "auto" {
		return defaultValue
	} else {
		result, err := strconv.ParseInt(strings.TrimSpace(v), 10, 32)
		if err != nil {
			panic(fmt.Sprintf("Attribute value %q must be a valid integer", v))
		}

		return int(result)
	}
}

func int16Attr(key string, defaultValue int16) int16 {
	v, ok := currentElementAttrs[key]
	if !ok || v == "auto" {
		return defaultValue
	} else {
		result, err := strconv.ParseInt(strings.TrimSpace(v), 10, 16)
		if err != nil {
			panic(fmt.Sprintf("Attribute value %q must be a valid integer", v))
		}

		return int16(result)
	}
}

// 获取bool类型的属性值。
func boolAttr(key string, defaultValue bool) bool {
	v, ok := currentElementAttrs[key]
	if !ok || v == "auto" {
		return defaultValue
	} else {
		v = strings.TrimSpace(v)
		switch v {
		case "true", "yes", "on", "1":
			return true
		case "false", "no", "off", "0":
			return false
		default:
			panic(fmt.Sprintf("Attribute value %q must be a valid bool", v))
		}
	}
}

// 获取字段类型的属性。
func fieldTypeAttr(key string, defaultValue DbFieldType) DbFieldType {
	v, ok := currentElementAttrs[key]
	if !ok || v == "auto" {
		return defaultValue
	} else {
		v = strings.TrimSpace(v)
		switch v {
		case "string":
			return FtString
		case "int", "integer":
			return FtInteger
		case "currency", "decimal", "money":
			return FtDecimal
		case "double", "real":
			return FtDouble
		case "bool", "boolean", "bit":
			return FtBoolean
		case "date", "datetime", "timestamp":
			return FtDateTime
		case "binary":
			return FtBinary
		case "enum":
			return FtEnum
		default:
			panic(fmt.Sprintf("Attribute %q must be a valid field type", v))
		}
	}
}

// 获取命令返回类型的属性值。
func resultTypeAttr(key string, defaultValue DbResultType) DbResultType {
	v, ok := currentElementAttrs[key]
	if !ok || v == "auto" {
		return defaultValue
	} else {
		v = strings.TrimSpace(v)
		switch v {
		case "entities":
			return RtEntities
		case "entity":
			return RtEntity
		case "string":
			return RtString
		case "int", "integer":
			return RtInteger
		case "currency", "decimal", "money":
			return RtDecimal
		case "double", "real":
			return RtDouble
		case "date", "datetime", "timestamp":
			return RtDateTime
		case "bool", "boolean", "bit":
			return RtBoolean
		case "binary":
			return RtBinary
		case "array", "objects":
			return RtObjects
		case "string-list":
			return RtStringList
		case "int-list", "integer-list":
			return RtIntegerList
		case "currency-list", "decimal-list", "money-list":
			return RtDecimalList
		case "double-list":
			return RtDoubleList
		case "date-list", "datetime-list", "timestamp-list":
			return RtDateTimeList
		case "bool-list", "boolean-list":
			return RtBooleanList
		case "binary-list":
			return RtBinaryList
		case "array-list", "objects-list":
			return RtObjectsList
		case "uc", "updated-count":
			return RtUpdatedCount
		default:
			panic(fmt.Sprintf("Attribute value %q must be a valid result type", v))
		}
	}
}

func parameterTypeAttr(key string, defaultValue DbParameterType) DbParameterType {
	v, ok := currentElementAttrs[key]
	if !ok || v == "auto" {
		return defaultValue
	} else {
		v = strings.TrimSpace(v)
		switch v {
		case "string":
			return PtString
		case "int", "integer":
			return PtInteger
		case "currency", "decimal", "money":
			return PtDecimal
		case "double", "real":
			return PtDouble
		case "bool", "boolean", "bit":
			return PtBoolean
		case "date", "datetime", "timestamp":
			return PtDateTime
		case "binary":
			return PtBinary
		case "int-set", "integer-set":
			return PtIntegerSet
		case "string-set":
			return PtStringSet
		default:
			panic(fmt.Sprintf("Attribute value %q must be a valid parameter type", v))
		}
	}
}

func parameterMatchAttr(key string, defaultValue DbParameterMatch) DbParameterMatch {
	v, ok := currentElementAttrs[key]
	if !ok || v == "auto" {
		return defaultValue
	} else {
		v = strings.TrimSpace(v)
		switch v {
		case "exact":
			return PmExact
		case "starts-with":
			return PmStartsWith
		case "ends-with":
			return PmEndsWith
		case "contains":
			return PmContains
		default:
			panic(fmt.Sprintf("Attribute value %q must be a parameter match", v))
		}
	}
}

// 触发panic，原因是没有获得期望的元素。
func panicXmlElement(expectedElementType string) {
	panic(fmt.Sprintf("Xml element must be %q", expectedElementType))
}

// 触发panic，原因是没有获得期望的属性。
func panicXmlAttr(attrName string, value interface{}) {
	panic(fmt.Sprintf("Attribute %q must be %v", attrName, value))
}

func pushElement(element DbObject) {
	dbObjects = append(dbObjects, element)
}

func popElement() DbObject {
	l := len(dbObjects)
	if l == 0 {
		panic("Stack is emPty!")
	}

	result := dbObjects[l-1]
	dbObjects = dbObjects[0 : l-1]

	return result
}

func peekElement() DbObject {
	l := len(dbObjects)
	if l == 0 {
		return nil
	}

	return dbObjects[l-1]
}

func pushSource(source string) {
	dbSources = append(dbSources, source)
}

func popSource() string {
	l := len(dbSources)
	if l == 0 {
		panic("No source file can be parsed!")
	}

	result := dbSources[l-1]
	dbSources = dbSources[0 : l-1]

	return result
}

func peekSource() string {
	l := len(dbSources)
	if l == 0 {
		panic("No source file can be parsed!")
	}

	return dbSources[l-1]
}

func appendEntity(e *DbEntity) {
	if e == nil {
		panic("Argument \"e\" must not be nil")
	}

	for _, item := range dbEntities {
		if item.Name == e.Name && item.Package == e.Package {
			panic(fmt.Sprintf("Duplicated entity %q of %q", e.Name, e.Package))
		}
	}

	dbEntities = append(dbEntities, e)
}

func appendModule(m *DbModule) {
	if m == nil {
		panic("Argument \"m\" must not be nil")
	}

	for _, item := range dbModules {
		if item.Name == m.Name {
			panic(fmt.Sprintf("Duplicated module %q of %q", m.Name, m.Package))
		}
	}

	dbModules = append(dbModules, m)
}

// 开始root元素。
func enterRoot(lineInfo *LineInfo) {
	package_ := nameAttr("package")

	pushElement(&DbRoot{
		LineInfo:    *lineInfo,
		Package:     package_,
		Description: stringAttr("description", ""),
		Timestamp:   currentFileTimestamp,
	})
}

// 结束root元素。
func exitRoot() {
	if _, ok := popElement().(*DbRoot); ok {
		return
	} else {
		panicXmlElement("root")
	}
}

// 开始include元素。
func enterInclude(lineInfo *LineInfo) {
	src := stringAttr("src", "")
	if src == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", "include.src"))
	}

	pushSource(filepath.Join(filepath.Dir(peekSource()), src))

	parseTopDbXml()
}

// 结束include元素。
func exitInclude() {
	popSource()
}

// 开始entity元素。
func enterEntity(lineInfo *LineInfo) {
	name := nameAttr("name")
	if name == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", "entity.name"))
	}

	if root, ok := peekElement().(*DbRoot); ok {
		pushElement(&DbEntity{
			LineInfo:    *lineInfo,
			Package:     root.Package,
			Name:        name,
			Description: stringAttr("description", ""),
			Timestamp:   root.Timestamp,
			Fields:      []*DbField{},
		})
	} else {
		panicXmlElement("root")
	}
}

// 结束entity元素。
func exitEntity() {
	if entity, ok := popElement().(*DbEntity); ok {
		appendEntity(entity)
	} else {
		panicXmlElement("entity")
	}
}

// 开始field元素。
func enterField(lineInfo *LineInfo) {
	name := nameAttr("name")
	if name == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", "field.name"))
	}

	if _, ok := peekElement().(*DbEntity); ok {
		pushElement(&DbField{
			LineInfo:     *lineInfo,
			Name:         name,
			Description:  stringAttr("description", ""),
			Type:         fieldTypeAttr("type", FtString),
			EnumType:     classNameAttr("enumType"),
			DefaultValue: rawAttr("defaultValue", rawAttr("default", "")),
			Key:          boolAttr("key", false),
		})
	} else {
		panicXmlElement("entity")
	}
}

// 结束field元素。
func exitField() {
	if field, ok := popElement().(*DbField); ok {
		if entity, ok := peekElement().(*DbEntity); ok {
			entity.AddField(field)
		}
	} else {
		panicXmlElement("field")
	}
}

// 开始module元素。
func enterModule(lineInfo *LineInfo) {
	name := nameAttr("name")
	if name == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", "module.name"))
	}

	if root, ok := peekElement().(*DbRoot); ok {
		pushElement(&DbModule{
			LineInfo:    *lineInfo,
			Package:     root.Package,
			Name:        name,
			Description: stringAttr("description", ""),
			Timestamp:   root.Timestamp,
			Sqls:        []*DbSql{},
			Commands:    []DbCommand{},
		})
	} else {
		panicXmlElement("root")
	}
}

// 结束module元素。
func exitModule() {
	if module, ok := popElement().(*DbModule); ok {
		appendModule(module)
	} else {
		panicXmlElement("module")
	}
}

// 开始sql元素。
func enterSql(lineInfo *LineInfo) {
	name := nameAttr("name")
	if name == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", "sql.name"))
	}

	pushElement(&DbSql{
		LineInfo: *lineInfo,
		Name:     name,
		Content:  "",
	})
}

// 结束sql元素。
func exitSql() {
	if sql, ok := popElement().(*DbSql); ok {
		if module, ok := peekElement().(*DbModule); ok {
			module.AddSql(sql)
		}
	} else {
		panicXmlElement("sql")
	}
}

// 开始statement元素。
func enterStatement(lineInfo *LineInfo) {
	name := nameAttr("name")
	if name == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", "statement.name"))
	}

	if _, ok := peekElement().(*DbModule); ok {
		entityName := stringAttr("entityName", "")
		var resultType DbResultType
		if entityName != "" {
			// 如果指定了实体名，那么默认返回类型是实体。
			resultType = resultTypeAttr("resultType", RtEntity)
		} else {
			// 否则默认返回类型是影响记录数。
			resultType = resultTypeAttr("resultType", RtUpdatedCount)
		}
		var readOnly bool
		var autoKey string
		if resultType != RtUpdatedCount {
			// 如果返回类型不是影响记录数，那么默认只读
			readOnly = boolAttr("readOnly", true)
			autoKey = ""
		} else {
			// 否则默认不是只读。
			readOnly = boolAttr("readOnly", false)
			// 然后获取autoKey
			autoKey = stringAttr("autoKey", "")
		}

		pushElement(&DbStatement{
			LineInfo:    *lineInfo,
			Name:        name,
			Description: stringAttr("description", ""),
			ResultType:  resultType,
			EntityName:  entityName,
			ReadOnly:    readOnly,
			AutoKey:     autoKey,
			Texts:       []DbText{},
			Parameters:  []*DbParameter{},
		})
	} else {
		panicXmlElement("module")
	}
}

// 结束statement元素。
func exitStatement() {
	if statement, ok := popElement().(*DbStatement); ok {
		if module, ok := peekElement().(*DbModule); ok {
			module.AddCommand(statement)
		}
	} else {
		panicXmlElement("statement")
	}
}

// 开始procedure元素。
func enterProcedure(lineInfo *LineInfo) {
	name := nameAttr("name")
	if name == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", "procedure.name"))
	}
	text := stringAttr("text", "")
	if text == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", "procedure.text"))
	}

	if _, ok := peekElement().(*DbModule); ok {
		entityName := stringAttr("entityName", "")
		var resultType DbResultType
		if entityName != "" {
			// 如果指定了实体名，那么默认返回类型是实体。
			resultType = resultTypeAttr("resultType", RtEntity)
		} else {
			// 否则默认返回类型是影响记录数。
			resultType = resultTypeAttr("resultType", RtUpdatedCount)
		}
		var readOnly bool
		if resultType != RtUpdatedCount {
			// 如果返回类型不是影响记录数，那么默认只读
			readOnly = boolAttr("readOnly", true)
		} else {
			// 否则默认不是只读。
			readOnly = boolAttr("readOnly", false)
		}

		pushElement(&DbProcedure{
			LineInfo:    *lineInfo,
			Name:        name,
			Text:        text,
			Description: stringAttr("description", ""),
			ResultType:  resultType,
			EntityName:  entityName,
			ReadOnly:    readOnly,
			Parameters:  []*DbParameter{},
		})
	} else {
		panicXmlElement("module")
	}
}

// 结束procedure元素。
func exitProcedure() {
	if procedure, ok := popElement().(*DbProcedure); ok {
		if module, ok := peekElement().(*DbModule); ok {
			module.AddCommand(procedure)
		}
	} else {
		panicXmlElement("procedure")
	}
}

// 开始parameter元素。
func enterParameter(lineInfo *LineInfo) {
	name := nameAttr("name")
	if name == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", "statement.name"))
	}

	if _, ok := peekElement().(DbCommand); ok {
		type_ := parameterTypeAttr("type", PtString)
		var match DbParameterMatch
		var size int16
		if type_ == PtString {
			match = parameterMatchAttr("match", PmExact)
			size = int16Attr("size", 0)
		} else if type_ == PtBinary {
			match = PmExact
			size = int16Attr("size", 0)
		} else {
			// 对于非字符串参数，match属性必须设置为exact，size属性必须设置为0。
			match = PmExact
			size = 0
		}
		pushElement(&DbParameter{
			LineInfo:    *lineInfo,
			Name:        name,
			Description: stringAttr("description", ""),
			Type:        type_,
			EnumType:    classNameAttr("enumType"),
			Size:        size,
			Match:       match,
		})
	} else {
		panicXmlElement("statement or procedure")
	}
}

// 结束parameter元素。
func exitParameter() {
	if parameter, ok := popElement().(*DbParameter); ok {
		if command, ok := peekElement().(DbCommand); ok {
			command.AddParameter(parameter)
		}
	} else {
		panicXmlElement("parameter")
	}
}

// 开始sqlRef元素。
func enterSqlRef(lineInfo *LineInfo) {
	name := nameAttr("name")
	if name == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", "sqlRef.name"))
	}

	if _, ok := peekElement().(*DbStatement); ok {
		pushElement(&DbSqlRef{LineInfo: *lineInfo, Name: name})
	} else {
		panicXmlElement("statement")
	}
}

// 结束sqlRef元素。
func exitSqlRef() {
	if sqlRef, ok := popElement().(*DbSqlRef); ok {
		if statement, ok := peekElement().(*DbStatement); ok {
			statement.AddSqlRef(sqlRef)
		}
	} else {
		panicXmlElement("sqlRef")
	}
}

func enterDynamicText(lineInfo *LineInfo) {
	if _, ok := peekElement().(*DbStatement); ok {
		prepend := strings.TrimSpace(stringAttr("prepend", ""))
		append := strings.TrimSpace(stringAttr("append", ""))
		joint := strings.TrimSpace(stringAttr("joint", "AND"))

		if prepend != "" {
			prepend = prepend + " "
		}
		if append != "" {
			append = " " + append
		}
		if joint != "" {
			joint = " " + joint + " "
		}

		pushElement(&DbDynamicText{
			LineInfo: *lineInfo,
			Prepend:  prepend,
			Append:   append,
			Joint:    joint,
			Chunks:   []*DbDynamicChunk{},
		})
	} else {
		panicXmlElement("statement")
	}
}

func exitDynamicText() {
	if dynamicText, ok := popElement().(*DbDynamicText); ok {
		if statement, ok := peekElement().(*DbStatement); ok {
			statement.AddDynamicText(dynamicText)
		}
	} else {
		panicXmlElement("dynamic")
	}
}

func enterDynamicChunk(elementName string, lineInfo *LineInfo) {
	parameter := nameAttr("parameter")
	if parameter == "" {
		panic(fmt.Sprintf("Attribute %q must not be blank", elementName+".parameter"))
	}

	if _, ok := peekElement().(*DbDynamicText); ok {
		pushElement(&DbDynamicChunk{
			LineInfo:  *lineInfo,
			Name:      elementName,
			Parameter: parameter,
			Value:     stringAttr("value", ""),
			Content:   "",
		})
	} else {
		panicXmlElement("dynamic")
	}
}

func exitDynamicChunk(elementName string) {
	if dynamicChunk, ok := popElement().(*DbDynamicChunk); ok {
		if dynamicChunk.Name == elementName {
			if dynamicText, ok := peekElement().(*DbDynamicText); ok {
				dynamicText.AddChunk(dynamicChunk)
			}
		} else {
			panicXmlElement(dynamicChunk.Name)
		}
	} else {
		panicXmlElement("isNull or isNotNull or equals or notEquals or in or notIn")
	}
}

func onStartElement(elementName string, lineInfo *LineInfo) {
	switch elementName {
	case "root":
		enterRoot(lineInfo)
	case "include":
		enterInclude(lineInfo)
	case "entity":
		enterEntity(lineInfo)
	case "field":
		enterField(lineInfo)
	case "module":
		enterModule(lineInfo)
	case "sql":
		enterSql(lineInfo)
	case "statement":
		enterStatement(lineInfo)
	case "procedure":
		enterProcedure(lineInfo)
	case "parameter":
		enterParameter(lineInfo)
	case "sqlRef":
		enterSqlRef(lineInfo)
	case "dynamic":
		enterDynamicText(lineInfo)
	case "isNull", "isNotNull", "isEmpty", "isNotEmpty", "isBlank", "isNotBlank", "equals", "notEquals", "in", "notIn":
		enterDynamicChunk(elementName, lineInfo)
	default:
		panic(fmt.Sprintf("Unknown element name: %q", elementName))
	}
}

func onEndElement(elementName string) {
	switch elementName {
	case "root":
		exitRoot()
	case "include":
		exitInclude()
	case "entity":
		exitEntity()
	case "field":
		exitField()
	case "module":
		exitModule()
	case "sql":
		exitSql()
	case "statement":
		exitStatement()
	case "procedure":
		exitProcedure()
	case "parameter":
		exitParameter()
	case "sqlRef":
		exitSqlRef()
	case "dynamic":
		exitDynamicText()
	case "isNull", "isNotNull", "isBlank", "isNotBlank", "equals", "notEquals", "in", "notIn":
		exitDynamicChunk(elementName)
	default:
		panic(fmt.Sprintf("Unknown element name: %q", elementName))
	}
}

func onText(text string) {
	if ele, ok := peekElement().(DbMixed); ok {
		ele.AddText(text)
	} else {
		s2 := strings.TrimSpace(text)
		if s2 != "" {
			panic(fmt.Sprintf("Character data must be blank, but is %q", s2))
		}
	}
}

// 解析栈顶的源文件。
func parseTopDbXml() {
	f, err := os.Open(peekSource())
	if err != nil {
		panic(err)
	}

	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		panic(err)
	}

	currentFileTimestamp = fi.ModTime()

	decoder := xml.NewDecoder(f)

	var token xml.Token
	level := 0
	for token, err = decoder.Token(); token != nil; token, err = decoder.Token() {
		if startElement, ok := token.(xml.StartElement); ok {
			elementName := startElement.Name.Local
			lineInfo := &LineInfo{peekSource(), 0} // 无法获取行号，所以传入0

			PrintVerbose("%sProcessing %q (%s)...", indent(level), elementName, lineInfo)
			level++

			currentElementAttrs = make(map[string]string)
			for _, attr := range startElement.Attr {
				currentElementAttrs[attr.Name.Local] = attr.Value
			}

			onStartElement(elementName, lineInfo)
		} else if endElement, ok := token.(xml.EndElement); ok {
			elementName := endElement.Name.Local

			level--
			PrintVerbose("%sProcessed %q", indent(level), elementName)

			onEndElement(elementName)
		} else if charData, ok := token.(xml.CharData); ok {
			if len(charData) > 0 {
				onText(string(charData))
			}
		}
	}
	if err != io.EOF {
		panic(err)
	}
}

// 解析指定的源文件，返回代码结构。
func ParseDbXml(source string) ([]*DbEntity, []*DbModule) {
	source, err := filepath.Abs(source)
	if err != nil {
		panic(err)
	}

	dbSources = []string{source}
	dbObjects = []DbObject{}
	dbEntities = []*DbEntity{}
	dbModules = []*DbModule{}

	parseTopDbXml()

	return dbEntities, dbModules
}
